import requests
from bs4 import BeautifulSoup
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
import smtplib
import random
import time
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.header import Header

# === 配置中心 ===
CITY_URL = "https://www.weather.com.cn/weather/101010100.shtml"  # 北京
SEND_EMAIL = "你的QQ邮箱@qq.com"
EMAIL_PASSWORD = "你的授权码"
RECEIVE_EMAIL = "收件人邮箱@qq.com"
SMTP_SERVER = "smtp.qq.com"
SMTP_PORT = 465
IMAGE_PATH = "weather_chart.png"

# === 全局设置 ===
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]

USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/112.0",
]


# === 增强型天气数据爬取 ===
def crawl_weather_data():
    try:
        # 随机延迟和请求头
        time.sleep(random.uniform(2, 5))
        headers = {
            "User-Agent": random.choice(USER_AGENTS),
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
            "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
            "Connection": "keep-alive",
            "Referer": "https://www.baidu.com/s?wd=天气",
        }

        print(f"🚀 开始爬取天气数据: {CITY_URL}")
        response = requests.get(CITY_URL, headers=headers, timeout=20)
        response.raise_for_status()

        # 强制设置编码为UTF-8，覆盖默认的latin-1
        response.encoding = "utf-8"
        print(f"✅ 响应编码已设置为: {response.encoding}")

        # 保存页面用于调试
        with open("weather_page.html", "w", encoding="utf-8") as f:
            f.write(response.text)
        print("✅ 页面已保存到 weather_page.html")

        soup = BeautifulSoup(response.text, "html.parser")

        # 调试：输出页面标题
        page_title = soup.title.text if soup.title else "未知标题"
        print(f"✅ 页面标题: {page_title}")

        # 尝试多种方式解析AQI
        aqi = 0
        aqi_elem = soup.find("div", class_="li6 hot")
        if aqi_elem and aqi_elem.find("span"):
            aqi = int(aqi_elem.find("span").text)
        else:
            # 备选方案：尝试通过文本查找AQI
            aqi_text = soup.find(string=lambda text: "AQI" in str(text))
            if aqi_text:
                aqi = int(''.join(filter(str.isdigit, str(aqi_text))))
            print(f"⚠️ 使用备选方案获取AQI: {aqi}")

        # 尝试多种方式解析预报数据
        forecast = []
        forecast_items = None

        # 方案1: 传统方式
        items = soup.find("ul", class_="t clearfix")
        if items:
            forecast_items = items.find_all("li")[:4]
            print(f"✅ 找到预报数据(方式1): {len(forecast_items)}天")

        # 方案2: 如果方式1失败，尝试其他方式
        if not forecast_items:
            items = soup.find("div", class_="forecast-container")
            if items:
                forecast_items = items.find_all("div", class_="forecast-item")[:4]
                print(f"✅ 找到预报数据(方式2): {len(forecast_items)}天")

        # 方案3: 如果前两种方式都失败，尝试通过日期查找
        if not forecast_items:
            print("⚠️ 使用备选方案查找预报数据")
            date_items = soup.find_all("div", class_="date-item")[:4]
            weather_items = soup.find_all("div", class_="weather-item")[:4]
            temp_items = soup.find_all("div", class_="temp-item")[:4]

            if len(date_items) >= 4 and len(weather_items) >= 4 and len(temp_items) >= 4:
                forecast_items = zip(date_items, weather_items, temp_items)
                print(f"✅ 找到预报数据(方式3): {len(date_items)}天")

        # 解析预报数据
        if forecast_items:
            for i, item in enumerate(forecast_items):
                try:
                    # 根据不同的查找方式提取数据
                    if isinstance(item, tuple):  # 方式3
                        date_elem, weather_elem, temp_elem = item
                        date_text = date_elem.text.strip()
                        weather = weather_elem.text.strip()
                        temp_text = temp_elem.text.strip().replace("℃", "")
                    else:  # 方式1和方式2
                        date_elem = item.find("h1") or item.find("div", class_="date")
                        date_text = date_elem.text.strip() if date_elem else f"第{i + 1}天"

                        weather_elem = item.find("p", class_="wea") or item.find("div", class_="weather")
                        weather = weather_elem.text.strip() if weather_elem else "未知"

                        temp_elem = item.find("p", class_="tem") or item.find("div", class_="temp")
                        temp_text = temp_elem.text.strip().replace("℃", "") if temp_elem else "0~0"

                    # 处理日期
                    if date_text == "今天":
                        date = datetime.now().strftime("%Y-%m-%d")
                    elif date_text == "明天":
                        date = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d")
                    else:
                        date = (datetime.now() + timedelta(days=i)).strftime("%Y-%m-%d")

                    # 解析温度范围
                    temp_min, temp_max = 0, 0
                    if "~" in temp_text:
                        temp_min, temp_max = temp_text.split("~")
                    elif "/" in temp_text:
                        temp_max, temp_min = temp_text.split("/")
                    elif "到" in temp_text:
                        parts = temp_text.split("到")
                        if len(parts) == 2:
                            temp_min, temp_max = parts
                    else:
                        temp_min = temp_max = temp_text

                    # 转换为整数
                    temp_min = int(temp_min.strip())
                    temp_max = int(temp_max.strip())

                    forecast.append({
                        "fxDate": date,
                        "textDay": weather,
                        "tempMin": temp_min,
                        "tempMax": temp_max
                    })
                except Exception as e:
                    print(f"⚠️ 解析第{i + 1}天数据失败: {str(e)}")
                    continue

        if not forecast:
            print("❌ 未能获取任何预报数据")
            return None

        print(f"✅ 成功获取{len(forecast)}天预报数据")
        for day in forecast:
            print(f"  • {day['fxDate']}: {day['textDay']}, {day['tempMin']}~{day['tempMax']}℃")

        return {
            "now": {"aqi": aqi},
            "forecast": forecast[1:],
            "tomorrow": forecast[1]
        }
    except requests.exceptions.RequestException as e:
        print(f"❌ 网络请求失败: {str(e)}")
        return None
    except Exception as e:
        print(f"❌ 爬取过程出错: {str(e)}")
        # 打印响应内容前100字符（检查是否包含乱码）
        if 'response' in locals() and hasattr(response, 'text'):
            print(f"响应内容预览: {response.text[:100]}")
        else:
            print("响应内容不可用")
        return None


# === 图表生成函数 ===
def generate_chart(forecast_data):
    try:
        dates = [day["fxDate"][5:] for day in forecast_data]
        highs = [day["tempMax"] for day in forecast_data]
        lows = [day["tempMin"] for day in forecast_data]

        plt.figure(figsize=(8, 4))
        plt.plot(dates, highs, "o-", color="#FF5722", label="最高温")
        plt.plot(dates, lows, "o-", color="#2196F3", label="最低温")
        plt.title("未来3天温度趋势")
        plt.xlabel("日期")
        plt.ylabel("℃")
        plt.legend()
        plt.grid(alpha=0.3)
        plt.tight_layout()
        plt.savefig(IMAGE_PATH, dpi=300, bbox_inches="tight")
        plt.close()
        print("✅ 图表生成成功")
        return True
    except Exception as e:
        print(f"❌ 图表生成失败: {str(e)}")
        return False


# === 邮件内容生成函数 ===
def create_email(reasons, tomorrow, aqi, chart_path):
    msg = MIMEMultipart()
    msg["Subject"] = Header(f"⚠️ 明日天气提醒：{tomorrow['fxDate']}", "utf-8").encode()
    msg["From"] = SEND_EMAIL
    msg["To"] = RECEIVE_EMAIL

    html = f"""
    <html>
    <head>
        <meta charset="utf-8">
        <style>
            body {{ font-family: SimHei, sans-serif; margin: 20px; }}
            .container {{ max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #eee; border-radius: 8px; }}
            h2 {{ color: #FF4444; margin-bottom: 15px; }}
            p {{ margin: 8px 0; }}
            ul {{ margin-top: 5px; padding-left: 25px; }}
            img {{ max-width: 100%; border-radius: 5px; margin-top: 15px; }}
            .alert {{ color: #FF4444; font-weight: bold; }}
        </style>
    </head>
    <body>
        <div class="container">
            <h2>明日天气提醒</h2>
            <p><strong>日期：</strong>{tomorrow['fxDate']}</p>
            <p><strong>天气：</strong>{tomorrow['textDay']}</p>
            <p><strong>温度：</strong>{tomorrow['tempMin']}℃ ~ {tomorrow['tempMax']}℃</p>
            <p><strong>空气质量：</strong>AQI {aqi} 
                {'<span class="alert">(污染严重)</span>' if aqi >= 150 else ''}
            </p>

            <h3>提醒原因：</h3>
            <ul>
                {''.join([f'<li>{reason}</li>' for reason in reasons])}
            </ul>

            <h3>未来3天温度趋势</h3>
            <img src="cid:chart" alt="温度趋势图">
        </div>
    </body>
    </html>
    """

    msg.attach(MIMEText(html, "html", "utf-8"))

    try:
        with open(chart_path, "rb") as f:
            img = MIMEImage(f.read())
            img.add_header("Content-ID", "<chart>")
            msg.attach(img)
    except Exception as e:
        print(f"⚠️ 嵌入图表失败: {str(e)}")
        # 添加纯文本替代内容
        text_alternative = MIMEText("由于技术问题，无法显示图表。请查看附件或访问天气网站获取详情。", "plain", "utf-8")
        msg.attach(text_alternative)

    return msg


# === 检查提醒条件 ===
def check_condition(weather_data):
    if not weather_data or not weather_data.get("tomorrow"):
        return {"should_remind": False, "reasons": [], "tomorrow": {}, "aqi": 0}

    tomorrow = weather_data["tomorrow"]
    aqi = weather_data["now"]["aqi"]
    reasons = []

    # 检查恶劣天气
    bad_weather_keywords = ["雨", "雪", "雷", "雹", "雾", "霾", "沙尘暴", "大风", "台风"]
    if any(word in tomorrow["textDay"] for word in bad_weather_keywords):
        reasons.append(f"恶劣天气：{tomorrow['textDay']}")

    # 检查空气质量
    if aqi >= 150:
        reasons.append(f"空气污染：AQI {aqi}（建议减少户外活动）")

    # 检查温度
    if tomorrow["tempMax"] >= 32:
        reasons.append(f"高温预警：{tomorrow['tempMax']}℃（注意防暑降温）")
    elif tomorrow["tempMax"] >= 35:
        reasons.append(f"极端高温：{tomorrow['tempMax']}℃（建议避免户外活动）")

    if tomorrow["tempMin"] <= 5:
        reasons.append(f"低温预警：{tomorrow['tempMin']}℃（注意保暖）")
    elif tomorrow["tempMin"] <= 0:
        reasons.append(f"严寒预警：{tomorrow['tempMin']}℃（注意防寒防冻）")

    return {
        "should_remind": len(reasons) > 0,
        "reasons": reasons,
        "tomorrow": tomorrow,
        "aqi": aqi
    }


# === 主函数 ===
def send_reminder():
    print("\n===== 开始执行天气提醒程序 =====")

    # 检查配置是否正确
    if not all([CITY_URL, SEND_EMAIL, EMAIL_PASSWORD, RECEIVE_EMAIL]):
        print("❌ 配置不完整，请检查CITY_URL、SEND_EMAIL、EMAIL_PASSWORD和RECEIVE_EMAIL")
        return

    weather_data = crawl_weather_data()
    if not weather_data:
        print("❌ 无法获取天气数据，程序终止")
        return

    condition = check_condition(weather_data)
    if not condition["should_remind"]:
        print("✅ 明日天气良好，无需提醒")
        return

    print(f"🔍 触发提醒原因：{', '.join(condition['reasons'])}")

    if not generate_chart(weather_data["forecast"]):
        print("⚠️ 图表生成失败，但继续发送邮件")

    msg = create_email(condition["reasons"], condition["tomorrow"], condition["aqi"], IMAGE_PATH)
    if not msg:
        print("❌ 邮件内容创建失败")
        return

    try:
        print(f"🚀 开始发送邮件至 {RECEIVE_EMAIL}...")
        with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT) as server:
            server.login(SEND_EMAIL, EMAIL_PASSWORD)
            server.send_message(msg)
        print("✅ 邮件发送成功！")
    except smtplib.SMTPAuthenticationError:
        print("❌ 授权失败：请检查邮箱地址或授权码是否正确")
    except Exception as e:
        print(f"❌ 邮件发送失败：{str(e)}")


# === 执行程序 ===
if __name__ == "__main__":
    send_reminder()